/*
 *  mm/memory.c
 *
 *  last modified at 2007.07.15 11:15 by ChenJie
 *
 */

#include <asm/i386.h>
#include <cos/kernel.h>
#include <cos/mm.h>
#include <cos/sched.h>

//物理内存大小：
unsigned long mm_size;



//物理内存分配映射表：
static unsigned char *mm_map;

//映射表映射的总页数：
static unsigned long  mm_map_size;

//映射表有效映射的开始页号：
static unsigned long  mm_map_begin;

//当前物理内存分配指针：
static unsigned long mm_alloced = 1*1024*1024;

/*
 *  设置分页机制
 *
 *  Param:
 *  	void
 *  
 *  Return:
 *  	void
 *
 *  Global Variables:
 *  	mm_size		R	物理内存大小
 *  	mm_alloced	M	物理内存的当前分配指针
 *  	pg_dir		R	页目录表
 *  	%cr0		M	设置PG=1
 *	%cr3		M	设置页目录地址为pg_dir
 *
 *  Function:
 *  	1.修改mm_alloced, 为对物理内存进行线性地址映射分配所需要页表
 *  	2.设置页目录表和页表, 对物理内存进行线性地址映射
 *  	3.开启cr0的PG位, 启用分页机制
 */
static void setup_paging(void)
{
	int i;

	// mm_addr用于模拟遍历物理内存
	unsigned long mm_addr=0;

	// pg_addr指向的是mm_alloced开始处的页地址
	unsigned long * pg_addr = (unsigned long *)PAGE_ALIGN(mm_alloced);

	// 对物理内存进行线性页表映射所需要的空间如下：
	// 每映射一页需要 sizeof(unsigned long) 字节
	// 总共有 mm_size >> 12 页
	mm_alloced = PAGE_ALIGN(mm_alloced)
		+ (mm_size>>12) * sizeof(unsigned long);

	// 清空页目录表
	for(i=0; i<1024; i++)
		pg_dir[i] = 0;

	// 设置pg_dir的页目录表中的每一项
	while((unsigned long)pg_addr < mm_alloced) {
		/*
		 * 设置页目录表项
		 * 	mm_addr>>22 是内存以4MB为单位分块, 地址mm_addr所在的块
		 * 		    号, 而页目录表中的每一项就代表这样的一块
		 * 	pg_addr 是页表的地址
		 * 	PG_USS 保证了映射的物理内存仅Kernel可以访问
		 */		    
		pg_dir[mm_addr>>22]=(unsigned long)pg_addr+(PG_P|PG_RWW|PG_USS);

		// 设置页表中的每一项的映射
		for(i=0; i<1024; i++){

			// 判断是否已将所有物理内存映射完
			if((unsigned long)pg_addr < mm_alloced){
				/*
				 * 设置页表项, 此时物理内存还未映射完
				 * 	pg_addr 指向页表中当前项的指针
				 * 	mm_addr 物理内存地址, 应是页地址
				 */
				*pg_addr = mm_addr+(PG_P|PG_RWW|PG_USS);
			}else{
				/*
				 * 设置该页表项无效, 当访问该页表项产生异常
				 */
				*pg_addr = 0;
			}
			// mm_addr 指向物理内存的下一页
			mm_addr += 1<<12;
			// pg_addr 指向页表的下一项
			pg_addr ++;
		}//此时完成一个目录项的映射
	}//此时完成所有目录项的映射

	/*
	 * 设置页目录地址, 启用cr0的PG位
	 */
	__asm__("\tmovl %%eax, %%cr3\n"		//设置页目录地址
		"\tmovl %%cr0, %%eax\n"
		"\torl $0x80000000, %%eax\n"	//开启cr0的PG标志
		"\tmovl %%eax, %%cr0\n"
		::"a"(pg_dir)
		);
}

/*
 *  内存管理初始化
 *
 *  Param:
 *  	ext_mm_size, 扩展内存大小, 以KB为单位
 *
 *  Return:
 *  	void
 *
 *  Global Variables:
 *  	mm_size		M	设定了物理内存大小
 *  	mm_map		M	设定了内存分配表的起始位置
 *  	mm_map_size	M	设定了内存分配表的大小
 *  	mm_alloced	M	为内存映射表分配了内存
 *  	EXT_MM_START	R	指明扩展内存从1MB处开始
 *
 *  Function:
 *  	1.计算物理内存大小 mm_size
 *  	2.启用分页机制
 *  	3.为内存分配表分配内存并初始化
 *
 */
void mm_init(unsigned long ext_mm_size)
{
	unsigned long i;

	// mm_size = 物理内存大小(以字节为单位)
	mm_size = (EXT_MM_START + ext_mm_size) << 10;

	// 确保mm_size最大只能为MM_MAX_MEMORY
	if(mm_size > MM_MAX_MEMORY)
		mm_size = MM_MAX_MEMORY;

	// 设定mm_alloced的起始位置
	mm_alloced = EXT_MM_START << 10;
	
	// 启用分页机制
	setup_paging();

	//进行页面对齐：
	mm_alloced = PAGE_ALIGN(mm_alloced);

	//为映射表分配内存：
	mm_map = (unsigned char *)mm_alloced;

	//映射表的大小：
	mm_map_size = (mm_size-(unsigned long)mm_map)>>12;

	//有效映射页号：
	mm_map_begin = (PAGE_ALIGN(mm_map_size))>>12;

	//设定映射状态：
	for(i=0; i<mm_map_size; i++){
		if(i<mm_map_begin) {
			mm_map[i] = MM_BUSY;
		}else{
			mm_map[i] = MM_FREE;
		}
	}
}

/*
 *  功能：
 *  	分配1页内存，返回其起始物理地址
 *  参数：
 *  	void
 *  返回：
 *  	所分配的页的物理内存地址, 值为0时表明分配失败
 *  全局变量：
 *  	mm_map		R
 *  	mm_map_size	R
 *  	mm_map_begin	R
 *  处理：
 *	1.遍历mm_map寻找MM_FREE的单元, 找到则返回其对就的页地址,否则返回0
 */
unsigned long mm_alloc_page(void)
{
	unsigned long i;
	unsigned long *page;
	static unsigned long last_alloc_page = 0;
	//从上次分配的页面开始查找空闲页面
	for(i=last_alloc_page;i<mm_map_size;i++) {
		if(mm_map[i] == MM_FREE) 
			break;
	}
	//判断是否找到空闲页面
	if(i==mm_map_size) {//此时未找到空闲页面,尝试从头开始查找：
		for(i=mm_map_begin;i<last_alloc_page;i++){
			if(mm_map[i] == MM_FREE)
				break;
		}
		if(i==last_alloc_page)//此时未找到空闲页面
			return 0;
	}
	//此时找找空闲页面
	last_alloc_page = i;
	mm_map[i] = MM_BUSY;
	page = (unsigned long *)(((unsigned long)(mm_map))+ (i<<12));
	for(i=0; i<1024; i++) page[i] = 0;
	return (unsigned long)page;
}

/*
 *  功能：
 *  	释放指定物理内存起始的1页内存
 *  参数：
 *  	p_addr		待释放的物理内存页的地址
 *  返回：
 *  	void
 *  全局变量：
 *  	mm_map		R
 *  	mm_map_size	R
 *  	mm_map_begin	R
 *  处理：
 *	1.将p_addr转换成对应的页号, 设置分配表的相应分配标志
 */
void mm_free_page(unsigned long p_addr)
{
	unsigned long i;
	//无需对p_addr进行页边界验证，直接释放其所在页
	i = (p_addr - (unsigned long)mm_map) >> 12;

	// 判断i是否有效
	if(i<mm_map_begin || i>=mm_map_size){
		panic("");
	}
	// 判断对应区域是否空闲
	if(mm_map[i]==MM_FREE)
		panic("[mm_free_page] error");
	// 将对该物理内存块的引用计数减1
	mm_map[i] --;
}


/*
 * 功能:
 * 	复制内核页表为用户页表, 仅用于Task0.
 * 输入:
 * 	kernel_start	假定是从4MH对齐的地址开始, task0是从0开始
 *	kernel_size	假定是640KB
 *	user_start	假定是从4MB对齐的地址开始, task0是从1GB开始
 * 输出:
 * 	无
 * 全局变量:
 * 	pg_dir		Task0的页目录表
 * 处理:
 * 	1.复制所有内核页表成为用户态页表.
 * 	2.设置用户态页表的P_USU属性.
 */
void copy_kernel_pages_to_user(unsigned long * pg_dir,
		unsigned long kernel_start, 
		unsigned long kernel_size,
		unsigned long user_start)
{
	unsigned long p, i;
	unsigned long *user_page, *kernel_page;
	for(p=0; p<kernel_size; p+=(1<<22)){
		// 获取内核的映射页表
		kernel_page = 
		    (unsigned long *)PAGE_ADDR(pg_dir[(kernel_start+p)>>22]);
		// 为用户分配一页内存
		user_page = (unsigned long *)mm_alloc_page();
		// 将内核页表的内容加PG_USU后复制到用户页表
		for(i=0; i<1024; i++){
			user_page[i] = kernel_page[i]|PG_USU;
		}
		// 在页表目录中填入用户页表
		pg_dir[(user_start+p)>>22] = 
			((unsigned long)user_page)|PG_P|PG_RWW|PG_USU;
	}
}

/*
 *
 *
 */
unsigned long * create_pg_dir_from(unsigned long * old_pg_dir)
{
	unsigned long * new_pg_dir;
	int i;
	new_pg_dir = (unsigned long *)mm_alloc_page();
	if(new_pg_dir==NULL) return NULL;
	for(i=0;i<1024; i++){
		new_pg_dir[i] = old_pg_dir[i];
	}
	return new_pg_dir;
}

static long do_no_page(unsigned long *pg_dir, unsigned long linear_addr)
{
	unsigned long *p, *page;	
	int i;
	if((pg_dir[linear_addr>>22] & PG_P) == 0){
		p = (unsigned long *)mm_alloc_page();
		if(p==NULL) return FAILURE;
		for(i=0; i<1024; i++) p[i]=0;
		pg_dir[linear_addr>>22] = (unsigned long)p+(PG_P|PG_RWW|PG_USU);
		page = p;
	}else{
		page = (unsigned long *)(PAGE_ADDR(pg_dir[linear_addr>>22]));
	}
	if((page[(linear_addr & 0x003ff000)>>12] & PG_P)==0){
		p = (unsigned long *)mm_alloc_page();
		if(p==NULL) return FAILURE;
		for(i=0; i<1024; i++) p[i]=0;
		page[(linear_addr & 0x003ff000)>>12] = (unsigned long)p+(PG_P|PG_RWW|PG_USU);
	}
	return SUCCESS;
}

/*
 * 功能:
 * 	page_fault处理函数
 * 输入:
 *	flags
 *	linear_addr
 * 输出:
 * 	-1	未处理
 * 	0	处理成功
 * 全局变量:
 * 	
 * 处理:
 *
 */
int mm_do_page_fault(unsigned long flags, unsigned long linear_addr)
{
	// flags & 0x1 ---> P
	//	flags & 0x2 ---> W/R
	//	flags & 0x4 ---> U/S
	if(flags & 0x1){
		// do_wp_page();
		return FAILURE;
	}else{
		return do_no_page((unsigned long *)procs.running_proc->tss.cr3, linear_addr);
	}
}

